package com.luo.bbs.ums.webapi.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luo.bbs.ums.service.IUserService;
import com.luo.bbs.ums.webapi.mapper.BuildingMapper;
import com.luo.bbs.ums.webapi.mapper.HouseMapper;
import com.luo.bbs.ums.webapi.mapper.UserMapper;
import com.luo.bbs.ums.webapi.security.UserLoginPrincipal;
import com.luo.bbs.ums.webapi.security.UserUserDetails;
import com.luo.common.exception.BBSServiceException;
import com.luo.common.restful.ResponseCode;
import com.luo.pojo.Building;
import com.luo.pojo.House;
import com.luo.pojo.ums.dto.ChangePasswordDTO;
import com.luo.pojo.ums.dto.UserLoginDto;
import com.luo.pojo.ums.dto.UserRegistryDTO;
import com.luo.pojo.ums.dto.UserUpdateDTO;

import com.luo.pojo.ums.entity.User;
import com.luo.pojo.ums.vo.UserInfoVO;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * <p>
 * 用户基本（常用）信息表 服务实现类
 * </p>
 *
 * @author bin.cn
 * @since 2023-01-19
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private BuildingMapper buildingMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private HouseMapper houseMapper;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 用户注册
     *
     * @param userRegistryDTO
     */
    @Override
    public void registry(UserRegistryDTO userRegistryDTO) {
        QueryWrapper<Building> queryBuilding = new QueryWrapper<>();
        queryBuilding.eq("invite_code", userRegistryDTO.getInviteCode())
        .eq("enabled",1);
        Building building = buildingMapper.selectOne(queryBuilding);
        if (building == null) {
            throw new BBSServiceException(ResponseCode.NOT_FOUND, "邀请码不存在");
        }

        if (StringUtils.isEmpty(userRegistryDTO.getPassword()) || StringUtils.isEmpty(userRegistryDTO.getAckPassword())) {
            throw new BBSServiceException(ResponseCode.NOT_ACCEPTABLE, "密码不能为空");
        }
        if (!userRegistryDTO.getPassword().equals(userRegistryDTO.getAckPassword())) {
            throw new BBSServiceException(ResponseCode.NOT_ACCEPTABLE, "两次密码不一致");
        }

        User user = new User();
        BeanUtils.copyProperties(userRegistryDTO, user);
        String encodePassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encodePassword);
        user.setEnabled(1);
        if (StringUtils.isBlank(user.getNickname())) {
            user.setNickname("用户" + UUID.randomUUID().toString().substring(0, 13));
        }
        if (StringUtils.isBlank(user.getSex())) {
            user.setSex("保密");
        }

        int row = userMapper.insert(user);
        if (row != 1) {
            throw new BBSServiceException(ResponseCode.BAD_REQUEST, "系统繁忙请稍后重试！");
        }

    }

    /**
     * 校验用户名,手机号是否有重复
     *
     * @param value
     * @param type
     */
    @Override
    public void checkValue(String value, String type) {
        QueryWrapper<User> queryUser = new QueryWrapper<>();
        queryUser.eq(type, value);
        User user = userMapper.selectOne(queryUser);
        System.out.println(user);
        if (user != null) {
            if ("username".equals(type)) {
                throw new BBSServiceException(ResponseCode.CONFLICT, "用户名已存在");
            }
            if ("phone".equals(type)) {
                throw new BBSServiceException(ResponseCode.CONFLICT, "电话已存在");
            }
        }

    }

    /**
     * 用户登录
     */
    @Value("${jwt.secretKey}")
    private String secretKey;

    @Override
    public String login(UserLoginDto userLoginDto) {
        Authentication authentication =
                new UsernamePasswordAuthenticationToken(userLoginDto.getUsername(), userLoginDto.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authentication);
        UserUserDetails userDetails = (UserUserDetails) authenticate.getPrincipal();
        //被禁用无法登录
        User user = userMapper.selectById(userDetails.getId());
        if (user.getEnabled()==0){
            throw new BBSServiceException(ResponseCode.UNAUTHORIZED_DISABLED,"用户被禁用，请联系管理员");
        }
        Map<String, Object> claims = new HashMap<>();
        claims.put("username", userDetails.getUsername());
        claims.put("password", userDetails.getPassword());
        claims.put("id", userDetails.getId());
        claims.put("nickname", userDetails.getNickname());
        log.debug(userDetails.getUsername());
        String jwt = Jwts.builder()
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "jwt")
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24 * 7))
                .compact();
        log.debug("jwt: " + jwt);
        //修改上次登录时间
        LocalDateTime now = LocalDateTime.now();
        user.setGmtLastLogin(now);
        userMapper.updateById(user);
        return jwt;
    }

    /**
     * 用户登出
     *
     * @param token
     */
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Override
    public void logout(String token) {
        if (token != null && token.startsWith(tokenHead)) {
            String authToken = token.substring(tokenHead.length());
            //按天存储jwt，方便清理
            //SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            //String logoutKey = "logout_token_lock" + dateFormat.format(new Date());
            String logoutKey = "user_logout_token_lock:";
            Long add = stringRedisTemplate.boundSetOps(logoutKey).add(authToken);
            if (add == 0) {
                throw new BBSServiceException(ResponseCode.BAD_REQUEST, "当前用户已登出，请勿重复登出!");
            }
            log.debug("将jwt存储到Redis");
            SecurityContextHolder.clearContext();
        } else {
            throw new BBSServiceException(ResponseCode.BAD_REQUEST, "当前用户未登录！");
        }
    }

    @Override
    public UserInfoVO updateUser(UserUpdateDTO userUpdateDTO) {
        log.debug("接受的参数:{}",userUpdateDTO);
        User user = new User();
        BeanUtils.copyProperties(userUpdateDTO, user);
        //如果选择了楼栋信息和门户信息，再给门户ID赋值
        if (userUpdateDTO.getHouseName()!=null && userUpdateDTO.getBuildingName()!=null){
            QueryWrapper<House> eq = new QueryWrapper<House>().eq("name", userUpdateDTO.getHouseName()).
                    eq("building_name", userUpdateDTO.getBuildingName());
            House house = houseMapper.selectOne(eq);
            user.setHouseId(house.getId());
        }
        log.debug("用户参数:{}",user);
        userMapper.updateById(user);
        //返回新的用户信息，存储在前端
        UserInfoVO userInfoVO = new UserInfoVO();
        user = userMapper.selectById(user.getId());
        BeanUtils.copyProperties(user,userInfoVO);
        return userInfoVO;
    }

    @Override
    public void renewPassword(ChangePasswordDTO changePasswordDTO, String token) {
        //校验输入的参数是否为空,是否一致
        validatedPassword(changePasswordDTO);
        //校验原密码是否正确
        Integer userId = getUserId();
        User user = userMapper.selectById(userId);
        System.out.println(changePasswordDTO.getPassword());
        System.out.println(passwordEncoder.encode(changePasswordDTO.getPassword()));
        System.out.println(user.getPassword());
        boolean matches = passwordEncoder.matches(changePasswordDTO.getPassword(), user.getPassword());
        System.out.println(matches);
        if (!matches){
            throw new BBSServiceException(ResponseCode.BAD_REQUEST,"密码不正确！");
        }
        //新密码插入
        String newPassword = passwordEncoder.encode(changePasswordDTO.getNewPassword());
        user.setPassword(newPassword);
        userMapper.updateById(user);
        //新密码，需要重新登录
        //获取token，jwt
        String authToken = token.substring(tokenHead.length());
        //将token放入Redis，即退出登录
        String logoutKey = "logout_token_lock:" ;
        stringRedisTemplate.boundSetOps(logoutKey).add(authToken);
        SecurityContextHolder.clearContext();
    }

    private void validatedPassword(ChangePasswordDTO changePasswordDTO) {
        if (changePasswordDTO.getPassword()==null){
            throw new BBSServiceException(ResponseCode.BAD_REQUEST,"原密码不能为空!");
        }
        if (changePasswordDTO.getAckNewPassword()==null){
            throw new BBSServiceException(ResponseCode.BAD_REQUEST,"确认新密码为空");
        }
        if (changePasswordDTO.getNewPassword()==null){
            throw new BBSServiceException(ResponseCode.BAD_REQUEST,"新密码为空");
        }
        if (!StringUtils.equals(changePasswordDTO.getNewPassword(), changePasswordDTO.getAckNewPassword())){
            throw new BBSServiceException(ResponseCode.BAD_REQUEST,"两次输入不一致");
        }
    }


    public UserLoginPrincipal getUserInfo() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        UserLoginPrincipal principal = (UserLoginPrincipal) authentication.getPrincipal();
        if (principal == null) {
            throw new BBSServiceException(ResponseCode.UNAUTHORIZED, "未登录！");
        }
        return principal;
    }

    public Integer getUserId() {
        if (getUserInfo() == null) {
            return null;
        }
        return getUserInfo().getId();
    }
}
